package cn.demoncat.util.lang;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 正则表达式
 *
 * <pre>
 *	单个字符：
 *	[abc]			多选 				表示此字符位可以是a、b或c
 * 	[^ab]  			排除   				^在匹配符[]内表示排除。表示此字符位不能是a和b。
 * 	[a-z]  			范围 				表示此字符位可以是a到z之间的字符
 * 	[a-go-z]  		多范围 				表示此字符位可以是a-g和o-z
 * 	[a-d[m-p]] 		并集 				表示此字符位可以是a-d或m到p，同于[a-dm-p]
 * 	[a-e][i-u] 		并集 				表示两个字符
 * 	[a-z&&[aei]] 	交集 				满足a-z，并且满足[]中的多选，同于[aei]。
 * 	[a-z&&[^m-p]]	交集
 *
 *  特殊字符：
 * 	\\d 			[0-9]				数字
 * 	\\D 			[^0-9]				非数字
 * 	\\w 			[a-zA-Z0-9_]		数字字母下划线
 * 	\\W 			[^\w]				非数字字母下划线
 * 	\\s 			[\t\n\x0B\f\r]		空白字符
 * 	\\S 			[^\s]				非空白字符
 *
 * 	数量：
 *  {2} 								出现2次
 * 	{2, 4} 								至少出现2次，最多出现4次
 * 	{2,} 								至少出现2次
 * 	{0,} 			* 					0到任意多次
 * 	{1,} 			+ 					1到任意多次
 * 	{0,1} 			? 					0次或1次
 *
 * 	位置：
 * 	^ 				行的开头，要求首字符匹配时使用：在[ ]外为起始，在[ ]内为排除。
 * 	$ 									行的结尾，要求尾字符匹配时使用
 * 	\b 									单词边界，要求之后的内容不能是单词
 *
 * 	分组：
 * 	() 		封装多个正则匹配字符为组，可以实现重用，或作为整体被数量或位置符号约束。
 * 	\\n		每个组都有编号(括号顺序，从1开始)，可以使用 \\编号 的形式在引号内调用。
 * 	$n 		非同一个引号内，使用 $编号 来获取组。整体表达式为0组。
 * 	\\n+ 	组作为整体，可以在分组时被数量匹配约束，也可以在调用时被数量匹配约束。
 * 	(()()) 	多重分组。使用时数左括号的数量，即分组的数量，以及编号。
 *
 * </pre>
 * 
 * @author 延晓磊
 *
 * @since 2017-3-23
 */
public final class RegexpUtil {

	/**
	 * 全文匹配
	 * 
	 * @param content 字符串
	 * @param regExp  正则表达式（全文匹配才为True）
	 * @return
	 * @author 延晓磊
	 *
	 * @since 2017-3-23
	 */
	public static boolean matcher(String content, String regExp) {
		Pattern pattern = Pattern.compile(regExp);
		Matcher matcher = pattern.matcher(content);
		return matcher.matches();
	}

	/**
	 * 部分匹配
	 * 
	 * @param content 字符串
	 * @param regExp  正则表达式（子串匹配即为True）
	 * @return
	 * 
	 * @author 延晓磊
	 *
	 * @since 2020-9-16
	 */
	public static boolean find(String content, String regExp) {
		Pattern pattern = Pattern.compile(regExp);
		Matcher matcher = pattern.matcher(content);
		return matcher.find();
	}

	/**
	 * 获取匹配的部分
	 *
	 * @param content 字符串
	 * @param regExp  正则表达式（子串匹配即为True）
	 * @return 空表示无匹配
	 *
	 * @author 延晓磊
	 *
	 * @since 2020-9-16
	 */
	public static List<String> search(String content, String regExp) {
		Pattern pattern = Pattern.compile(regExp);
		Matcher matcher = pattern.matcher(content);
		List<String> groups = new ArrayList<>();
		while (matcher.find()){
			groups.add(matcher.group());
		}
		return groups;
	}

	/**
	 * 匹配邮箱验证码
	 */
	public static final String EMAIL_CODE = "^[0-9]{6}$";
	/**
	 * 匹配图片验证码
	 */
	public static final String IMG_CODE = "^[0-9]{4}$";
	/**
	 * 匹配邮箱验证码
	 */
	public static final String SMS_CODE = "^[0-9]{6}$";
	/**
	 * 匹配email地址
	 */
	public static final String EMAIL = "^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$";

	/**
	 * 匹配url
	 */
	public static final String URL = "^[a-zA-z]+://(\\w+(-\\w+)*)(\\.(\\w+(-\\w+)*))*(\\?\\S*)?$";

	/**
	 * 匹配中文字符
	 */
	public static final String CHINESE = "[\\u4e00-\\u9fa5]";

	/**
	 * 匹配中文字符串
	 */
	public static final String CHINESE_ALL = "[\\u4e00-\\u9fa5]+";

	/**
	 * 包含中文字符
	 */
	public static final String CHINESE_CONTAIN = ".*[\\u4e00-\\u9fa5]+.*";

	/**
	 * 匹配ASCII字符
	 */
	public static final String ASCII = "[\\x00-\\xff]";

	/**
	 * 匹配ASCII字符串
	 */
	public static final String ASCII_ALL = "[\\x00-\\xff]+";

	/**
	 * 包含ASCII字符
	 */
	public static final String ASCII_CONTAIN = ".*[\\x00-\\xff]+.*";

	/**
	 * 匹配空行
	 */
	public static final String NULL_LINE = "\\n[\\s?]*\\r";

	/**
	 * 匹配国内电话号码，匹配形式如 0511-4405222 或 021-87888822或 021-87888822-001
	 */
	public static final String TELEPHONE = "^(((0\\d2|0\\d{2})[- ]?)?\\d{7,8}|((0\\d3|0\\d{3})[- ]?)?\\d{7,8})(-\\d{3})?$";

	/**
	 * 腾讯QQ号, 腾讯QQ号从10000开始
	 */
	public static final String QQ = "^[1-9][0-9]{4,11}$";

	/**
	 * 匹配中国邮政编码
	 */
	public static final String POSTBODY = "^[1-9]\\d{5}(?!\\d)$";

	/**
	 * 匹配身份证, 中国的身份证为15位或18位
	 */
	public static final String ID_CARD = "^\\d{15}(\\w{3})?$";

	/**
	 * IP
	 */
	public static final String IP = "^\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$";

	/**
	 * 手机号
	 */
	public static final String MOBILE = "^1[3456789][0-9]{9}$";

	/**
	 * 银行卡
	 */
	public static final String BANK_CARD = "^\\d{6,30}$";

	/**
	 * 注释符号，用于通过str.replaceAll去除字符串中的注释
	 */
	public static final String COMMENT = "/\\*[\\s\\S]*?\\*/";

	/**
	 * 回车换行
	 */
	public static final String LINE = "\r?\n";

	/**
	 * 
	 * 描述：字母类型的匹配
	 *
	 * @author 延晓磊
	 *
	 * @since 2017年3月23日
	 */
	public static class AlphabetRex {
		/**
		 * 匹配由26个英文字母组成的字符串
		 */
		public static final String ALPHABET = "^[A-Za-z]+$";

		/**
		 * 匹配由26个英文字母的大写组成的字符串
		 */
		public static final String BIG_ALPHABET = "^[A-Z]+$";

		/**
		 * 匹配由26个英文字母的小写组成的字符串
		 */
		public static final String LOW_ALPHABET = "^[a-z]+$";

		/**
		 * 匹配由数字和26个英文字母组成的字符串
		 */
		public static final String ALPHABET_NUM = "^[A-Za-z0-9]+$";
	}

	/**
	 * 
	 * 描述：数值类型正则
	 *
	 * @author 延晓磊
	 *
	 * @since 2017年3月23日
	 */
	public static class NumberRex {
		/**
		 * 数字：0 | 整数 | 小数
		 */
		public static final String ALL = "^-?(0|[1-9][0-9]*)(\\.\\d+)?$";

		/**
		 * 正数：0 | 整数 | 小数
		 */
		public static final String POSITIVE = "^(0|[1-9][0-9]*)(\\.\\d+)?$";

		/**
		 * 负数：0 | 整数 | 小数
		 */
		public static final String NEGATIVE = "^-(0|[1-9][0-9]*)(\\.\\d+)?$";

		/**
		 * 整数：0 | 正整 | 负整
		 */
		public static final String INTEGER = "^0|(-?[1-9][0-9]*)$";

		/**
		 * 正整数
		 */
		public static final String POSITIVE_INTEGER = "^[1-9][0-9]*$";

		/**
		 * 负整数
		 */
		public static final String NEGATIVE_INTEGER = "^-[1-9][0-9]*$";

		/**
		 * 非负整数：0 | 正整
		 */
		public static final String NO_NEGATIVE_INTEGER = "^0|([1-9][0-9]*)$";

		/**
		 * 非正整数：0 | 负整
		 */
		public static final String NO_POSITIVE_INTEGER = "^0|(-[1-9][0-9]*)$";

		/**
		 * 小数：0 | 正浮 | 负浮
		 */
		public static final String FLOAT = "^-?(0|[1-9][0-9]*)(\\.\\d+)$";

		/**
		 * 非负小数：0 | 正小数
		 */
		public static final String NO_NEGATIVE_FLOAT = "^(0|[1-9][0-9]*)(\\.\\d+)$";

		/**
		 * 非正小数：0 | 负小数
		 */
		public static final String NO_POSITIVE_FLOAT = "^-(0|[1-9][0-9]*)(\\.\\d+)$";
	}
}
